#![allow(clippy::type_complexity)]

extern crate pretty_env_logger;
#[macro_use]
extern crate log;

use askama::Template;
use axum::extract;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Response};
use axum::routing::get_service;
use axum::{
    extract::Path,
    routing::{delete, get},
    Router,
};
use bigdecimal::BigDecimal;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::mysql::MySqlRow;
use sqlx::{mysql::MySqlPoolOptions, pool, MySql, MySqlPool, Pool};
use sqlx::{FromRow, Row};
use std::collections::HashSet;
use std::error::Error;
use std::f32::consts::E;
use std::str::FromStr;
use std::{collections::HashMap, time::Duration};
use std::{env, fmt};

use log::{debug, info, trace, warn};
use tokio::io;
use tower_http::services::{ServeDir, ServeFile};

fn get_sales_unit_name_code(name: String) -> String {
    let sales_unit_name_code = HashMap::from([
        ("陕西步长制药有限公司".to_string(), "2001".to_string()),
        ("山东丹红制药有限公司".to_string(), "2002".to_string()),
        ("山东步长医药销售有限公司".to_string(), "5001".to_string()),
        ("山东步长制药股份有限公司".to_string(), "1000".to_string()),
    ]);

    let code = match sales_unit_name_code.get(&name) {
        Some(v) => v.clone(),
        _ => "".to_string(),
    };

    code
}

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    pretty_env_logger::init();

    let args: Vec<String> = env::args().collect();

    if args.len() <= 4 {
        //println!("调用方式如下： check_utils \"南京医药(淮安)天颐有限公司\" \"陕西步长制药有限公司\" \"商务部\" \"脑心通%36\"");

        let serve_dir = get_service(ServeDir::new("assets")).handle_error(handle_error);

        // our router
        let app = Router::new()
            .route(
                "/export/:company_name/:sales_unit/:agent_name/:material_like_name",
                get(export),
            )
            .nest("/assets", serve_dir.clone());

        // run it with hyper on localhost:3000
        axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
            .serve(app.into_make_service())
            .await
            .unwrap();
    } else {
        let url = "mysql://omsuser:omsuser@10.2.18.23:3306/oms-admin";
        let pool = MySqlPool::connect(url).await?;

        let company_name: String = String::from(args[1].as_str());
        let sales_unit_name: String = String::from(args[2].as_str());
        let angent_name = String::from(args[3].as_str());
        // angent_name = String::from("200072");
        let material_like_name = String::from(args[4].as_str());
    }

    // 客户总欠款

    // 客户总预收

    Ok(())
}

async fn export(
    Path((company_name, sales_unit, agent_name, material_like_name)): Path<(
        String,
        String,
        String,
        String,
    )>,
) -> impl IntoResponse {
    let init_831 = match query_for_export::<OmsCreditProductBeginningEnding>(
        &company_name,
        &sales_unit,
        &agent_name,
        &material_like_name,
    )
    .await
    {
        Ok(it) => it,
        Err(err) => {
            println!("query_for_export_t error: {:?}", err);
            vec![]
        }
    };

    let write_off_product = match query_for_export::<WriteOffProduct>(
        &company_name,
        &sales_unit,
        &agent_name,
        &material_like_name,
    )
    .await
    {
        Ok(it) => it,
        Err(err) => {
            println!("query_for_export_t error: {:?}", err);
            vec![]
        }
    };

    let returned = match query_for_export::<Returned>(
        &company_name,
        &sales_unit,
        &agent_name,
        &material_like_name,
    )
    .await
    {
        Ok(it) => it,
        Err(err) => {
            println!("query_for_export_t error: {:?}", err);
            vec![]
        }
    };

    let order_delivery = match query_for_export::<OrderDelivery>(
        &company_name,
        &sales_unit,
        &agent_name,
        &material_like_name,
    )
    .await
    {
        Ok(it) => it,
        Err(err) => {
            println!("query_for_export_t error: {:?}", err);
            vec![]
        }
    };

    let invoice_return_item = match query_for_export::<InvoiceReturnItem>(
        &company_name,
        &sales_unit,
        &agent_name,
        &material_like_name,
    )
    .await
    {
        Ok(it) => it,
        Err(err) => {
            println!("query_for_export_t error: {:?}", err);
            vec![]
        }
    };

    

    HtmlTemplate(ExportTemplate {
        init_831,
        write_off_product,
        returned,
        order_delivery,
        invoice_return_item
    })
}

async fn query_for_export<T>(
    company_name: &String,
    sales_unit_name: &String,
    agent_name: &String,
    material_like_name: &String,
) -> Result<Vec<T>, sqlx::Error>
where
    T: Entity,
{
    let url = "mysql://omsuser:omsuser@10.2.18.23:3306/oms-admin";
    let pool = MySqlPoolOptions::new()
        .max_connections(10)
        .connect(url)
        .await?;

    let sql = T::get_sql(
        company_name,
        sales_unit_name,
        agent_name,
        material_like_name,
    );

    println!("{}", sql);

    let query_res = sqlx::query(sql.as_str()).fetch_all(&pool).await?;

    let mut res: Vec<T> = vec![];

    for r in query_res {
        res.push(T::from(r));
    }

    Ok(res)
}

#[derive(Template)]
#[template(path = "export.html")]
struct ExportTemplate {
    init_831: Vec<OmsCreditProductBeginningEnding>,
    write_off_product: Vec<WriteOffProduct>,
    returned: Vec<Returned>,
    order_delivery: Vec<OrderDelivery>,
    invoice_return_item: Vec<InvoiceReturnItem>,
}

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut res = String::from("");
        let arg = &self.0;
        println!("{:?}", arg.len());
        arg.into_iter().for_each(|r| res.push_str(&r.to_string()));
        write!(f, "{:?}", res)
    }
}

struct HtmlTemplate<T>(T);

impl<T> IntoResponse for HtmlTemplate<T>
where
    T: Template,
{
    fn into_response(self) -> Response {
        match self.0.render() {
            Ok(html) => Html(html).into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. Error: {}", err),
            )
                .into_response(),
        }
    }
}

async fn handle_error(_err: io::Error) -> impl IntoResponse {
    (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
}

trait Entity {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String;
    fn from(r: MySqlRow) -> Self;
}

impl Entity for OmsCreditProductBeginningEnding {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String {
        let sales_unit = get_sales_unit_name_code(sales_unit_name.clone());

        format!("SELECT company_name, product_name, num_ending, amount_ending, debts_num_ending, debts_amount_ending,ending_time
        FROM `oms-credit`.oms_credit_product_beginning_ending
        where sales_unit='{sales_unit}' and company_name='{company_name}' and agent_name='{agent_name}'
        and product_name like '%{material_like_name}%' and ending_time like '2022-08-31%'")
    }

    fn from(r: MySqlRow) -> Self {
        OmsCreditProductBeginningEnding {
            company_name: r.get(0),
            product_name: r.get(1),
            num_ending: r.get(2),
            amount_ending: r.get(3),
            debts_num_ending: r.get(4),
            debts_amount_ending: r.get(5),
            ending_time: r.get(6),
        }
    }
}

impl Entity for WriteOffProduct {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String {
        let sales_unit = get_sales_unit_name_code(sales_unit_name.clone());
        format!(
        "select
        source_code, amount,cs.create_time, source.message_info->>'$.messageRoots[*].payload.omsOrderMaterial[*].writeOffCount' as write_off_count, source.message_info->>'$.messageRoots[*].payload.omsOrderMaterial[*].discountPrice' as discount_price
        from
        `oms-credit`.oms_credit_summary cs
        left join `oms-credit`.oms_credit_source source on cs.certificate_no = source.certificate_no
        where cs.sales_unit='{sales_unit}' 
        and cs.company_name='{company_name}' 
        and cs.source_classify = 6 and cs.source_credit_type = 6 
        and agent_name='{agent_name}' 
        and source.message_info like '%{material_like_name}%'"
        )
    }

    fn from(r: MySqlRow) -> Self {
        WriteOffProduct {
            source_code: r.get(0),
            amount: r.get(1),
            write_off_count: r.get(3),
            discount_price: r.get(4),
            create_time: r.get(2),
        }
    }
}

impl Entity for Returned {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String {
        let sales_unit = get_sales_unit_name_code(sales_unit_name.clone());

        format!(
            "select * from (
        SELECT
            CASE
                    WHEN r.channel = '11' THEN ou.nick_name
                    WHEN r.channel = 'S1' THEN '商务部'
            END AS 'agent_name',
            r.receivable_code 'receivable_code',
            r.recognize_amount 'recognize_amount',
            r.apply_time 'apply_time' ,
            rm.material_long_name,
            rm.total_amount
        FROM
            `oms-returned`.`oms_receivable_info` r 
            LEFT JOIN `oms-system`.`oms_user` ou ON ou.user_name = r.apply_user AND ou.del_flag = 0
            left join `oms-returned`.oms_resolve_material rm on r.receivable_no=rm.receivable_no
        WHERE
            -- r.create_time BETWEEN '2022-01-01' AND '2022-09-31' 
            -- AND r.receivable_type IN ( '0', '5' ) 
            -- AND r.type = '0' 
            r.payer_name='{company_name}'
            and r.sales_unit='{sales_unit}'
        ) s
        where s.`agent_name`='{agent_name}'
        and s.material_long_name like '%{material_like_name}%'"
        )
    }

    fn from(r: MySqlRow) -> Self {
        Returned {
            receivable_code: r.get(1),
            recognize_amount: r.get(2),   // '认款金额'
            apply_time: r.get(3),         //'认款申请时间' ,
            material_long_name: r.get(4), // 产品名称
            total_amount: r.get(5),
        }
    }
}

impl Entity for OrderDelivery {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String {
        let sales_unit = get_sales_unit_name_code(sales_unit_name.clone());

        let mut sql = format!(
            "SELECT
            `oc`.`dict_label` AS `事业部`,
            `oa`.`company_name`,
            `ou`.`nick_name` AS `agent_name`,
            `ooi`.`order_type` ,
            `ooi`.`order_code`,
            `ooi`.`sap_order_no` ,
            (
            CASE
                    `ooi`.`order_status` 
                    WHEN '0' THEN
                    '待提交' 
                    WHEN '1' THEN
                    '合同未回锁定' 
                    WHEN '2' THEN
                    '已驳回' 
                    WHEN '3' THEN
                    'GSP锁定' 
                    WHEN '4' THEN
                    '待发货' 
                    WHEN '5' THEN
                    '发货中' 
                    WHEN '6' THEN
                    '发货完成' 
                    WHEN '7' THEN
                    '审核中' 
                    WHEN '8' THEN
                    '待拆单' 
                    WHEN '9' THEN
                    '已关闭' 
                    WHEN '10' THEN
                    '已拒绝' 
                    WHEN '11' THEN
                    '推送失败' ELSE '未匹配' 
                END 
                ) AS `order_status`,
                ifnull(DATE_FORMAT(`ooi`.`examine_pass_time`,\"%Y-%m-%d %H:%i:%s\"), '-') as examine_pass_time,
                `om`.`material_long_name` ,
                `oom`.`material_count` ,
                `oom`.`actual_price` ,
                ifnull( `ood`.`deliver_quantity`, 0 ) AS `deliver_quantity` 
                FROM
                    (
                        (
                            (
                                (
                                    (
                                        (
                                            (
                                                (
                                                    (
                                                        (
                                                            ( `oms-order`.`oms_order_material` `oom` LEFT JOIN `oms-order`.`oms_order_info` `ooi` ON ( ( `oom`.`order_no` = `ooi`.`order_no` ) ) )
                                                            LEFT JOIN `oms-order`.`oms_order_address` `ooa` ON ( ( `ooa`.`order_no` = `ooi`.`order_no` ) ) 
                                                        )
                                                        LEFT JOIN (
                                                        SELECT
                                                            `oms-order`.`oms_order_delivery`.`order_no` AS `order_no`,
                                                            `oms-order`.`oms_order_delivery`.`matnr` AS `matnr`,
                                                            `oms-order`.`oms_order_delivery`.`psonr_s` AS `psonr_s`,
                                                            `oms-order`.`oms_order_delivery`.`deliver_quantity`  
                                                        FROM
                                                            `oms-order`.`oms_order_delivery` 
                                                        WHERE
                                                            ( ( `oms-order`.`oms_order_delivery`.`deliver_status` = 'C' ) AND ( `oms-order`.`oms_order_delivery`.`deliver_quantity` > 0 ) AND ( `oms-order`.`oms_order_delivery`.`del_flag` = 0 ) ) 
                                                    
                                                            ) `ood` ON (
                                                            ( ( `ood`.`order_no` = `oom`.`order_no` ) AND ( `ood`.`matnr` = `oom`.`matnr` ) AND ( `ood`.`psonr_s` = `oom`.`product_num` ) ) 
                                                        ) 
                                                    )
                                                    LEFT JOIN `oms-system`.`oms_dept` `dr` ON ( ( `dr`.`dept_id` = `ooi`.`dept_region` ) ) 
                                                )
                                                LEFT JOIN `oms-system`.`oms_dept` `da` ON ( ( `da`.`dept_id` = `ooi`.`dept_agency` ) ) 
                                            )
                                            LEFT JOIN `oms-system`.`oms_user` `ou` ON ( ( `ou`.`user_name` = `ooi`.`apply_user` ) ) 
                                        )
                                        LEFT JOIN `oms-customer`.`oms_archive` `oa` ON ( ( `oa`.`archive_no` = `ooi`.`customer_name` ) ) 
                                    )
                                    LEFT JOIN `oms-order`.`oms_order_close_message` `oocm` ON ( ( `oocm`.`order_no` = `ooi`.`order_no` ) ) 
                                )
                                LEFT JOIN (
                                SELECT
                                    `oms-system`.`oms_dict_data`.`dict_label` AS `dict_label`,
                                    `oms-system`.`oms_dict_data`.`dict_value` AS `dict_value` 
                                FROM
                                    `oms-system`.`oms_dict_data` 
                                WHERE
                                    ( ( `oms-system`.`oms_dict_data`.`dict_type` = 'oms_sale_type' ) AND ( `oms-system`.`oms_dict_data`.`del_flag` = 0 ) ) 
                                ) `ost` ON ( ( `ost`.`dict_value` = `ooi`.`sales_unit` ) ) 
                            )
                            LEFT JOIN (
                            SELECT
                                `oms-system`.`oms_dict_data`.`dict_label` AS `dict_label`,
                                `oms-system`.`oms_dict_data`.`dict_value` AS `dict_value` 
                            FROM
                                `oms-system`.`oms_dict_data` 
                            WHERE
                                ( ( `oms-system`.`oms_dict_data`.`dict_type` = 'oms_channel' ) AND ( `oms-system`.`oms_dict_data`.`del_flag` = 0 ) ) 
                            ) `oc` ON ( ( `oc`.`dict_value` = `ooi`.`channel` ) ) 
                        )
                        LEFT JOIN `oms-data`.`oms_material` `om` ON ( ( `om`.`material_no` = `oom`.`material_no` ) ) 
                    ) 
                WHERE
                    
                        ( `oom`.`del_flag` = 0 ) 
                        AND ( `ooi`.`del_flag` = 0 ) 
                        AND ( `ooa`.`del_flag` = 0 ) 
                        AND ( `ou`.`del_flag` = 0 ) 
                        AND ( `oa`.`del_flag` = 0 ) 
                        AND ( `om`.`del_flag` = 0 ) 
                    AND ( ( `oocm`.`del_flag` = 0 ) OR isnull( `oocm`.`del_flag` ) ) 
                    and examine_pass_time is not null
                    and `oa`.`company_name`= '{company_name}' and ooi.sales_unit='{sales_unit}' 
                    and `om`.material_long_name like '%{material_like_name}%'
            "
        );

        sql.push_str("");

        if !agent_name.eq("商务部") {
            sql.push_str(format!("and `ou`.`nick_name` like '%{agent_name}%'").as_str());
        }

        sql
    }

    fn from(r: MySqlRow) -> Self {
        OrderDelivery {
            company_name: r.get(1),
            agent_name: r.get(2),
            order_type: r.get(3),
            order_code: r.get(4),
            sap_order_no: r.get(5),
            order_status: r.get(6),
            material_long_name: r.get(8),
            examine_pass_time: r.get(7),
            material_count: r.get(9),
            actual_price: r.get(10),
            deliver_quantity: r.get(11),
        }
    }
}

impl Entity for InvoiceReturnItem {
    fn get_sql(
        company_name: &String,
        sales_unit_name: &String,
        agent_name: &String,
        material_like_name: &String,
    ) -> String {
        let sales_unit = get_sales_unit_name_code(sales_unit_name.clone());

        let mut sql = format!(
            "select * from (
                SELECT  distinct 
                                o.archive_name as company_name,
                                case o.channel
                        when '11' then ou.nick_name
                        when 's1' then '商务部'
                        END as agent_name,
                                o.order_code,
                                o.sap_order_no,
                                m.material_long_name,
                                r.invoice_date,
                                i.tax_included_amount,
                                i.item_no,
                                o.sales_unit,
                        o.channel,
                                i.matnr,
                        o.payment_type 
                        
                        FROM
                        `oms-order`.oms_order_invoice_return_item i
                        INNER JOIN (SELECT DISTINCT invoice_date,sap_invoice_no from `oms-order`.oms_order_invoice_return where del_flag=0 and invoice_tax_amount > 0)  r ON i.sap_invoice_no = r.sap_invoice_no
                        INNER JOIN `oms-order`.oms_order_info o ON i.order_code = o.order_code
                                LEFT JOIN `oms-system`.`oms_user` `ou` ON `ou`.`user_name` = `o`.`apply_user`  
                                left join `oms-data`.oms_material m on m.matnr = i.matnr and m.del_flag = 0
                        WHERE
                        r.invoice_date <= '2022-09-30 23:59:59'
                        AND o.apply_time <= '2022-09-30 23:59:59'
                        AND o.channel  in('S1','11')
                        AND i.del_flag = 0
                        AND o.del_flag = 0
                                ) s
                                where company_name = '{company_name}' and sales_unit='{sales_unit}' and material_long_name like '%{material_like_name}%' and agent_name='{agent_name}'"
        );

       
        sql
    }

    fn from(r: MySqlRow) -> Self {
        InvoiceReturnItem {
            company_name: r.get(0),
            agent_name: r.get(1),
            order_code: r.get(2),
            sap_order_no: r.get(3),
            material_long_name: r.get(4),
            invoice_date: r.get(5),
            tax_included_amount: r.get(6),
        }
    }
}

#[derive(Debug, sqlx::FromRow)]
struct OmsCreditProductBeginningEnding {
    company_name: String,
    product_name: String,
    num_ending: BigDecimal,
    amount_ending: BigDecimal,
    debts_num_ending: BigDecimal,
    debts_amount_ending: BigDecimal,
    ending_time: sqlx::types::chrono::NaiveDateTime,
}

#[derive(Debug, sqlx::FromRow)]
struct WriteOffProduct {
    source_code: String,
    amount: BigDecimal,
    write_off_count: String,
    discount_price: String,
    create_time: sqlx::types::chrono::NaiveDateTime,
}
#[derive(Debug, sqlx::FromRow)]
struct Returned {
    receivable_code: String,
    recognize_amount: BigDecimal,                   // '认款金额'
    apply_time: sqlx::types::chrono::NaiveDateTime, //'认款申请时间' ,
    material_long_name: String,                     // 产品名称
    total_amount: BigDecimal,                       // 产品分解金额
}
#[derive(Debug, sqlx::FromRow)]
struct OrderDelivery {
    company_name: String,
    agent_name: String,
    order_type: String,
    order_code: String,
    sap_order_no: String,
    order_status: String,
    material_long_name: String,
    examine_pass_time: String,
    material_count: i32,
    actual_price: BigDecimal,
    deliver_quantity: i64,
}
#[derive(Debug, sqlx::FromRow)]
struct InvoiceReturnItem {
    company_name: String,
    agent_name: String,
    order_code: String,
    sap_order_no: String,
    material_long_name: String,
    invoice_date: sqlx::types::chrono::NaiveDateTime,
    tax_included_amount: String,
}

